﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace GameSushi.TweenEngine
{
    class Tween : BaseTween
    {
        public Tween()
        {
            Reset();
        }

        // Implement proxy methods.
        internal sealed override void _Build() { Build(); }
        internal sealed override void _Start() { Start(); }
        internal sealed override void _Start(TweenManager manager) { Start(manager); }
        internal sealed override void _Delay(float delay) { Delay(delay); }
        internal sealed override void _Repeat(int count, float delay) { Repeat(count, delay); }
        
        // implement IBaseTween
        public virtual Tween Start()
        {
            InternalStart();
            return this;
        }

        public virtual Tween Start(TweenManager manager)
        {
            manager.Add(this);
            return this;
        }

        public virtual Tween Delay(float _delay)
        {
            this.delay += _delay;
            return this;
        }

        public virtual Tween Repeat(int count, float delay)
        {
            this.InternalRepeat(count, delay);
            return this;
        }

        public virtual Tween Build()
        {
            if (target == null)
            {
                return this;
            }

            accessor = (ITweenAccessor)registeredAccessors[targetClass];

            if (accessor == null && target is TweenAccessor<object>)
            {
                accessor = (TweenAccessor<object>)target;
            }

            if (accessor != null)
            {
                combineAttrsCount = accessor._GetValues(target, type, accessorBuffer);
            }
            else
            {
                throw new InvalidOperationException("No TweenAccessor was found for the target");
            }

            if (combineAttrsCount > combinedAttrsLimit)
            {
                ThrowCombinedAttrsLimitReached();
            }
            return this;
        }

        protected internal override void Reset()
        {
            base.Reset();
            target = null;
            targetClass = null;
            accessor = null;
            type = -1;
            isFrom = isRelative = false;
            combineAttrsCount = 0;
            if (accessorBuffer.Length != combinedAttrsLimit)
            {
                accessorBuffer = new float[combinedAttrsLimit];
            }
        }

        //overide InitializeOverride - UpdateOverride
        protected internal override void InitializeOverride()
        {
            if (target == null)
            {
                return;
            }

            //Gan' gia tri cua target -> startValues
            accessor._GetValues(target, type, startValues);

            for (int i = 0; i < combineAttrsCount; i++)
            {
                targetValues[i] += isRelative ? startValues[i] : 0;
                if (isFrom)
                {
                    float temp = startValues[i];
                    startValues[i] = targetValues[i];
                    targetValues[i] = temp;
                }
            }
        }

        protected internal override void UpdateOverride(int step, int lastStep, bool isIterationStep, float delta)
        {
            if (target == null)
            {
                return;
            }

            //if (!isIterationStep) {
            //    accessor._SetValues(target, type, targetValues);
            //    return;
            //}

            if (!isIterationStep && step > lastStep)
            {
                //finish roi. Gan gia tri cho target
                //accessor._SetValues(target, type, startValues);
                accessor._SetValues(target, type, IsReverse(lastStep) ? startValues : targetValues);
                return;
            }

            if (!isIterationStep && step < lastStep)
            {
                //accessor._SetValues(target, type, targetValues);
                accessor._SetValues(target, type, IsReverse(lastStep) ? targetValues : startValues);
                return;
            }

            //if (duration < 0.00000000001f && delta > -0.00000000001f)
            //{
            //    accessor._SetValues(target, type, IsReverse(step) ? targetValues : startValues);
            //    return;
            //}

            //if (duration < 0.00000000001f && delta < 0.00000000001f)
            //{
            //    accessor._SetValues(target, type, IsReverse(step) ? startValues : targetValues);
            //    return;
            //}

            float time = IsReverse(step) ? duration - GetCurrentTime() : GetCurrentTime();
            float t = (time / duration);

            for (int i = 0; i < combineAttrsCount; i++)
            {
                accessorBuffer[i] = startValues[i] + t * (targetValues[i] - startValues[i]);
            }
            

            accessor._SetValues(target, type, accessorBuffer);
        }

        protected internal override void ForceStartValues()
        {
            if (target == null)
            {
                return;
            }
            accessor._SetValues(target, type, startValues);
        }

        protected internal override void ForceEndValues()
        {
            if (target == null)
            {
                return;
            }
            accessor._SetValues(target, type, targetValues);
        }
        
        /// <summary>
        /// Tween
        /// </summary>
        
        private object target;
        private Type targetClass;
        private ITweenAccessor accessor;
        private int type;

        private bool isFrom;
        private bool isRelative;

        private static int combinedAttrsLimit = 3;
        private int combineAttrsCount;

        public static void SetCombineAttributesLimit(int limit) {
            Tween.combinedAttrsLimit = limit;
        }

        private static readonly IDictionary<Type, ITweenAccessor> registeredAccessors = new Dictionary<Type, ITweenAccessor>();

        public static void RegisterAccessor(Type someClass, ITweenAccessor defaultAccessor)
        {
            registeredAccessors[someClass] = defaultAccessor;
        }

        public static ITweenAccessor GetRegisteredAccessor(Type someClass)
        {
            return registeredAccessors[someClass];
        }

        private float[] accessorBuffer = new float[combinedAttrsLimit];

        private readonly float[] startValues = new float[combinedAttrsLimit];

        private readonly float[] targetValues = new float[combinedAttrsLimit];

        private void Setup(object target, int tweenType, float duration)
        {
            if (duration >= 0)//duration phai > 0
            {
                this.target = target;
                this.type = tweenType;
                this.targetClass = target != null ? FindTargetClass() : null;
                this.duration = duration;
            }   
        }

        public static TweenEngine.Tween To(object target, int tweenType, float duration)
        {
            TweenEngine.Tween tween = new Tween();
            tween.Setup(target, tweenType, duration);
            return tween;
        }

        public static TweenEngine.Tween Set(object target, int tweenType)
        {
            TweenEngine.Tween tween = new Tween();
            tween.Setup(target, tweenType, 0);
            return tween;
        }

        public static void KillAll(TweenManager tweenManager)
        {
            tweenManager.KillAll();
        }

        /// <summary>
        /// Target
        /// </summary>
        /// <param name="targetValue"></param>
        /// <returns></returns>
        public Tween Target(float targetValue)
        {
            targetValues[0] = targetValue;

            return this;
        }

        public Tween Target(float targetValue1, float targetValue2)
        {
            targetValues[0] = targetValue1;
            targetValues[1] = targetValue2;
            return this;
        }

        public Tween Target(float targetValue1, float targetValue2, float targetValue3)
        {
            targetValues[0] = targetValue1;
            targetValues[1] = targetValue2;
            targetValues[2] = targetValue3;
            return this;
        }

        public Tween Target(float[] _targetValues)
        {
            if (_targetValues.Length > combinedAttrsLimit)
            {
                ThrowCombinedAttrsLimitReached();
            }

            System.Array.Copy(_targetValues, 0, this.targetValues, 0, _targetValues.Length);
            return this;
        }

        private Type FindTargetClass()
        {
            if (registeredAccessors.ContainsKey(target.GetType()))
            {
                return target.GetType();
            }
            if (target is ITweenAccessor)
            {
                return target.GetType();
            }
            Type parentClass = target.GetType().BaseType;
            while (parentClass != null && !registeredAccessors.ContainsKey(parentClass))
            {
                parentClass = parentClass.BaseType;
            }
            return parentClass;
        }

        /// <summary>
        /// Get 
        /// </summary>
        /// <returns></returns>
        public object GetTarget()
        {
            return target;
        }

        public float[] GetTargetValues()
        {
            return targetValues;
        }

        public int GetCombinedAttributesCount()
        {
            return combineAttrsCount;
        }

        public ITweenAccessor GetAccessor()
        {
            return accessor;
        }

        public Type GetTargetClass()
        {
            return targetClass;
        }

        //message
        private void ThrowCombinedAttrsLimitReached()
        {
            string msg = "You cannot combine more than " + combinedAttrsLimit + " " + "attributes in a tween. You can raise this limit with "
                 + "Tween.setCombinedAttributesLimit(), which should be called once " + "in application initialization code.";
            throw new InvalidOperationException(msg);
        }
    }
}
